package de.home.gpio;

import java.util.HashMap;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.mockito.Mockito;

import com.pi4j.io.gpio.GpioController;
import com.pi4j.io.gpio.GpioFactory;
import com.pi4j.io.gpio.GpioPinDigitalInput;
import com.pi4j.io.gpio.GpioPinDigitalOutput;
import com.pi4j.io.gpio.Pin;

import de.home.exception.PinAlreadyInUseException;
import de.home.gpio.pinService.PinInputService;
import de.home.gpio.pinService.PinOutputService;
import de.home.gpio.pinService.PinService;
import de.home.gpio.pinService.PinSwitchService;
import de.home.util.Configuration;

public class GPIOService {

   private static final Logger  LOG = LogManager.getLogger(GPIOService.class);
   private GpioController       gpioController;
   private static GPIOService   gpioService;

   private Map<Pin, PinService> pinServices;

   private GPIOService() {
      try {
         init();
      } catch (Throwable e) {
         LOG.error(e.getMessage(), e);
      }
   }

   private void init() {
      pinServices = new HashMap<Pin, PinService>();
      if ( Configuration.getInstance().getBoolean("localEnv", false) ) {
         gpioController = Mockito.mock(GpioController.class);
      } else {
         gpioController = GpioFactory.getInstance();
      }
   }

   public synchronized static GPIOService getInstance() {
      if ( gpioService == null )
         gpioService = new GPIOService();
      return gpioService;
   }

   public void shutdown() {
      LOG.warn("GPIOController try to shutdown!");
      // gpioController.shutdown();
      // gpioController.unprovisionPin(switch433MhzPin);
      // gpioController.unexport(switch433MhzPin);
      gpioController.unexportAll();
      LOG.warn("GPIOController was shutdown!");
   }

   public PinService getService(Pin pin) {
      return pinServices.get(pin);
   }

   public void registerSwitchService(Pin pin) throws PinAlreadyInUseException {
      checkIfPinCanBeUsed(pin);
      GpioPinDigitalOutput switch433MhzPin = gpioController.provisionDigitalOutputPin(pin);
      PinSwitchService switchService = new PinSwitchService(switch433MhzPin);
      pinServices.put(pin, switchService);
   }

   public void registerPinOutputService(Pin pin) throws PinAlreadyInUseException {
      checkIfPinCanBeUsed(pin);
      GpioPinDigitalOutput outputPin = gpioController.provisionDigitalOutputPin(pin);
      PinOutputService pinOutputService = new PinOutputService(outputPin);
      pinServices.put(pin, pinOutputService);
   }

   public void registerPinInputService(Pin pin) throws PinAlreadyInUseException {
      checkIfPinCanBeUsed(pin);
      GpioPinDigitalInput inputPin = gpioController.provisionDigitalInputPin(pin);
      PinInputService pinInputService = new PinInputService(inputPin);
      pinServices.put(pin, pinInputService);
   }

   private void checkIfPinCanBeUsed(Pin pin) throws PinAlreadyInUseException {
      if ( pinServices.containsKey(pin) ) {
         throw new PinAlreadyInUseException("Pin " + pin.getAddress() + " is already used with "
               + pinServices.get(pin).getClass().getSimpleName() + ".");
      }
   }
}
